package com.edu.lawdoc.dao;

import com.edu.lawdoc.constant.Const;
import com.edu.lawdoc.errors.AppException;
import com.edu.lawdoc.errors.Errors;
import com.edu.lawdoc.generator.field.output.Column;
import com.jfinal.kit.Kv;
import com.jfinal.kit.LogKit;
import com.jfinal.plugin.activerecord.*;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Dao - 基类
 * 
 * 
 */
public class BaseDao<M extends Model<M>> {
	public final String SQL_FIELD = "sql_field";
	public final String SQL_FIELDS = "sql_fields";
	public final String SQL_MODIFYING_FIELDS = "sql_modifying_fields";
	public final String SQL_FILTERS = "sql_filters";
	public final String SQL_TABLE_NAME = "sql_tableName";
	public final String SQL_ORDER = "sql_order";

	static Pattern patternForSpace = Pattern.compile("\\s+");

	/** 实体类类型 */
	private Class<M> modelClass;
	
	protected M modelManager;
	
	public Class<M> getModelClass() {
		return modelClass;
	}

	public void setModelClass(Class<M> modelClass) {
		this.modelClass = modelClass;
	}

	/**
	 * 将多个空格变成一个空格
	 * @param str
	 * @return
	 */
	public static String cleanSpace(String str) {
		Matcher m = patternForSpace.matcher(str);
		return m.replaceAll(" ");
	}
	
	/**
	 * 构造方法
	 */
	public BaseDao(Class<M> entityClass) {
		//获得定义Class时声明的父类的范型参数的类型
		//this.setModelClass(GenericsKit.getSuperClassGenricType(entityClass));
		this.setModelClass(entityClass);
		try {
			modelManager = modelClass.newInstance();
		} catch (InstantiationException e) {
			LogKit.error("instance model fail!" + e);
		} catch (IllegalAccessException e) {
			LogKit.error("instance model fail!" + e);
		}
	}

	public M getDao(){
		return modelManager;
	}
	
	public String getTableName() {
		Table table = TableMapping.me().getTable(getModelClass());
		return table.getName();
	}

	public String[] getPrimaryKeys() {
		Table table = TableMapping.me().getTable(getModelClass());
		table.getPrimaryKey();
		return table.getPrimaryKey();
	}
	
	/**
	 * 查找实体对象
	 * 
	 * @param id
	 * @return 实体对象，若不存在则返回null
	 */
	public M findById(Long id, String[] fields) {
		if (id == null) {
			throw new AppException(Errors.ILLEGAL_PARAMETER.name(),
					String.format("ID: %s", id),
					"Parameter is blank while calling method findById()");
		}

		Filter filter = new Filter();
		filter.put(Column.ID, id);
		filter.put(Column.IS_DELETED, Const.n.name());
		List<M> list = this.find(fields, filter, null);
		if(list.isEmpty()) {
			throw new AppException(Errors.NO_RECORD_FOUND_BY_ID.name(),
					String.format("ID: %s", id),
					"The return list for calling method findById() is empty");
		}

		if(list.size() > 1) {
			throw new AppException(Errors.MORE_THAN_ONE_RECORDS_FOUND.name(),
					String.format("ID: %s", id),
					"Too many record found while calling method findById()");
		}
		return list.get(0);
	}

	/**
	 * 构造条件查询的SqlPara
	 * @param fields
	 * @param filter
	 * @param orderBy
	 * @return
	 */
	public SqlPara makeSqlPara(String[] fields, Filter filter, OrderBy orderBy) {
		filter.put(Column.IS_DELETED, Const.n.name());
		Kv cond = Kv.by(this.SQL_FIELDS, StringUtils.join(fields, ","))
				.set(this.SQL_FILTERS, filter.getPara())
				.set(this.SQL_TABLE_NAME, this.getTableName());
		if(orderBy != null) {
			cond.set(this.SQL_ORDER, orderBy);
		}
		SqlPara sqlPara = Db.getSqlPara("common.findList", cond);
		sqlPara.setSql(cleanSpace(sqlPara.getSql()).trim());
		return sqlPara;
	}

	/**
	 * 通用的条件查询
	 * @param fields
	 * @param filter
	 * @param orderBy
	 * @return
	 */
	public List<M> find(String[] fields, Filter filter, OrderBy orderBy){
		return this.modelManager.find(this.makeSqlPara(fields, filter, orderBy));
	}

	/**
	 * 通用查询数据，并取第一个数据
	 * @param fields
	 * @param filter
	 * @param orderBy
	 * @return
	 */
	public M findFirst(String[] fields, Filter filter, OrderBy orderBy) {
		return this.modelManager.findFirst(this.makeSqlPara(fields, filter, orderBy));
	}

	/**
	 * 构建update语句
	 * @param modifyingFields
	 * @param filter
	 * @return
	 */
	public SqlPara makeUpdateSqlPara(ModifyingFields modifyingFields, Filter filter) {
		filter.put(Column.IS_DELETED, Const.n.name());
		Kv cond = Kv.by(this.SQL_MODIFYING_FIELDS, modifyingFields.getPara())
				.set(this.SQL_FILTERS, filter.getPara())
				.set(this.SQL_TABLE_NAME, this.getTableName());
		SqlPara sqlPara = Db.getSqlPara("common.update", cond);
		sqlPara.setSql(cleanSpace(sqlPara.getSql()).trim());
		return sqlPara;
	}

	/**
	 * 通过id修改
	 * @param id
	 * @param version
	 * @param modifyingFields
	 */
	public void updateById(long id, int version, ModifyingFields modifyingFields) {
		Filter oFilter = new Filter();
		oFilter.put(Column.ID, id);
		oFilter.put(Column.VERSION, version);
		oFilter.put(Column.IS_DELETED, Const.n.name());

		if(modifyingFields.containsKey(Column.VERSION)) {
			throw new AppException(Errors.ILLEGAL_PARAMETER.name(),
					String.format("version: %s", version),
					"Don't modify version field in update by id action");

		}

		SqlPara sqlPara = this.makeUpdateSqlPara(modifyingFields, oFilter);
		int effectCount = Db.update(sqlPara);
		if(effectCount != 1) {
			throw new AppException(Errors.UPDATE_ERR.name(),
					String.format("id: %s, version: %s, effectCount: %s", id, version, effectCount),
					"updateById's effect count != 1");
		}
	}

	/**
	 * remove 的含义是把东西挪动到别处，还存在，所以可用于软删除
	 * @param id
	 * @param version
	 */
	public void removeById(long id, int version) {
		ModifyingFields modifyingFields = new ModifyingFields();
		modifyingFields.put(Column.IS_DELETED, Const.y.name());
		this.updateById(id, version, modifyingFields);
	}

	/**
	 * 构造删除语句
	 * @param id
	 * @param version
	 * @return
	 */
	public SqlPara makeDeleteSqlPara(long id, int version) {
		Filter filter = new Filter();
		filter.put(Column.ID, id);
		filter.put(Column.VERSION, version);
		filter.put(Column.IS_DELETED, Const.n.name());

		filter.useDataBaseIndex(Column.ID);

		Kv cond = Kv.by(this.SQL_FILTERS, filter.getPara())
				.set(this.SQL_TABLE_NAME, this.getTableName());
		SqlPara sqlPara = Db.getSqlPara("common.delete", cond);
		sqlPara.setSql(cleanSpace(sqlPara.getSql()).trim());
		return sqlPara;
	}

	/**
	 * 真实删除数据
	 * @param id
	 * @param version
	 */
	public void deleteById(long id, int version){
		int effectCount = Db.update(this.makeDeleteSqlPara(id, version));
		if(effectCount != 1) {
			throw new AppException(Errors.DELETE_ERR.name(),
					String.format("id: %s, version: %s, effectCount: %s", id, version, effectCount),
					"deleteById's effect count != 1;");
		}
	}

	/**
	 * 查询所有记录并分页
	 * @param pageNum
	 * @param pageSize
	 * @param fields
	 * @return
	 */
	public Page<M> paginate(Integer pageNum, Integer pageSize, String[] fields) {
		Filter filter = new Filter();
		OrderBy orderBy = new OrderBy();
		return this.paginate(pageNum, pageSize, fields, filter, orderBy);
	}


	/**
	 * 分页查询
	 * @param pageNum
	 * @param pageSize
	 * @param fields
	 * @param filter
	 * @param orderBy
	 * @return
	 */
	public Page<M> paginate(Integer pageNum, Integer pageSize, String[] fields, Filter filter, OrderBy orderBy) {
		SqlPara sqlPara = this.makeSqlPara(fields, filter, orderBy);
		return this.modelManager.paginate(pageNum, pageSize, sqlPara);
	}

	/**
	 * 构建批量删除语句
	 * @param field
	 * @param values
	 * @return
	 */
	public SqlPara makeDeleteBatchSqlPara(String field, Object... values) {
		if(StringUtils.isBlank(field)) {
			for(Object val : values) {
				if(!(val instanceof Long)) {
					throw new IllegalArgumentException("id value must be Long type");
				}
			}
			field = Column.ID;
		}

		if(values == null || values.length == 0) {
			throw new IllegalArgumentException("values can't be null or empty");
		}

		SqlPara sqlPara = Db.getSqlPara("common.deleteBatch",
				Kv.by("values", values).set(SQL_FIELD, field).set(SQL_TABLE_NAME, this.getTableName()));
		sqlPara.setSql(cleanSpace(sqlPara.getSql()).trim());
		return sqlPara;
	}

	/**
	 * 通过id，批量真实删除数据
	 * @param ids
	 * @return
	 */
	public boolean deleteBatchById(Object... ids) {
		return this.deleteBatch(null, ids);
	}

	/**
	 * 批量真实删除数据
	 * @param field
	 * @param values
	 * @return
	 */
	public boolean deleteBatch(String field, Object... values) {
		return Db.update(this.makeDeleteBatchSqlPara(field, values)) > 0;
	}
}